package med.movyzer.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.StringTokenizer;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

import med.movyzer.datastructs.Logger;
import med.movyzer.datastructs.MovementSeries;
import med.movyzer.util.ExtCalendar;

import org.jfree.data.time.TimeSeries;

public class GT3XData implements MovyzerDataInterface{
	private ArrayList<Integer[]> data=new ArrayList<Integer[]>();

	private String serialNumber;
	private String memoryAddress;
	private String batteryVoltage;
	private int mode;
	private String filePath;
	private boolean isValidFile = false;
	private ExtCalendar startTime=new ExtCalendar();
	private ExtCalendar endTime=new ExtCalendar();
	private ExtCalendar endShowTime=new ExtCalendar();
	private ExtCalendar downloadTime=new ExtCalendar();
	private ExtCalendar epochPeriod=new ExtCalendar();
	private int shift=0;

	//Handle Movyzer(mobile) Files. 
	private boolean mobileFile=false;
	private boolean rawFile=false;

	public static final int MOVEMENT_TIME_SHIFT_THRESHOLD=100;
	public static final int MOBILE_FILE_TRESHOLD=200;
	public static final int RAW_FILE_TRESHOLD=100;
	public static final int EARTH_GRAVITY=9810;
	
	private int earthGravity=EARTH_GRAVITY;
	private int treshold=RAW_FILE_TRESHOLD;

	public GT3XData() {
	}

	/**
	 * This Constructor is for the file choosers preview functionality.
	 * This Constructor is intended to be called by the custom movyzer File chooser only. 
	 * 
	 * @param f the currently selected file
	 */
	public GT3XData(File selectedFile) {
		loadFile(selectedFile.getPath(), null);
	}

	public ExtCalendar getEndTime() {
		return endTime;
	}

	public void setEndTime(ExtCalendar endTime) {
		this.endTime = endTime;
	}
	
	private static Double getPreviewIntensity(Integer[] data,boolean mobileFile,boolean rawFile){
		Integer[] currData=data.clone();
		Double intensity= getRawIntensity(currData);
		if(mobileFile){
			intensity=Math.abs(intensity-9810);
			if(intensity<MOBILE_FILE_TRESHOLD){
				intensity=0d;
			}
			intensity/=1000;
		}else if(rawFile){
			intensity=Math.abs(intensity-(EARTH_GRAVITY/10));
			if(intensity<RAW_FILE_TRESHOLD){
				intensity=0d;
			}
			intensity/=100;
		}
		return intensity;
	}

	private Double getMovementIntensity(Integer[] data){
		Integer[] currData=data.clone();
		Double intensity= getRawIntensity(currData);
		if(mobileFile){
			intensity=Math.abs(intensity-earthGravity);
			if(intensity<treshold){
				intensity=0d;
			}
			intensity/=1000;
		}else if(rawFile){
			intensity=Math.abs(intensity-(earthGravity/10));
			if(intensity<treshold){
				intensity=0d;
			}
			intensity/=100;
		}
		return intensity;
	}

	private static Double getRawIntensity(Integer[] data){
		double result=0;
		if(data.length==5){
			if (data[0]!=null&&data[1]!=null&&data[2]!=null){
				result=Math.pow(data[0],2)+Math.pow(data[1],2)+Math.pow(data[2],2);
			}
		}else{
			for(int i=0;i<data.length;i++){
				if(data[i]!=null){
					result+=Math.pow(data[i],2);
				}else{
					result+=0;
				}
			}
		}
		result=Math.sqrt(result);
		return result;
	}

	public MovementSeries getMovementSeries(int timeframeThreshold){
		MovementSeries result=new MovementSeries(startTime);	
		ExtCalendar currTime=(ExtCalendar) startTime.clone();
		currTime.add(Calendar.MILLISECOND, shift);

		for(int i=0;i<data.size()-1 && currTime.before(endShowTime);i++){			
			if(getMovementIntensity(data.get(i))>0){
				MovementSeries.Movement movement=new MovementSeries.Movement();
				movement.setStartTime(currTime);
				int n=0;
				int i_before=i;
				double currIntensity=getMovementIntensity(data.get(i));
				double avgIntensity=0;
				
				boolean isMovement=true;
				
				while(isMovement){
					for(n=0;currIntensity>0 && n+i<data.size()-2;n++){
						avgIntensity+=currIntensity;
						currTime.addTime(epochPeriod);
						currIntensity=getMovementIntensity(data.get(n+i+1));
					}
					i+=n;
					isMovement=false;
					if(epochPeriod.get(Calendar.MILLISECOND)!=0){
						int iterations=timeframeThreshold/epochPeriod.get(Calendar.MILLISECOND);
						for(n=0;n<iterations && n+i<data.size()-1;n++){
							currIntensity=getMovementIntensity(data.get(n+i));
							if(currIntensity>0){
								isMovement=true;
								for(int j=0;j<n;j++){
									currTime.addTime(epochPeriod);
								}
								i+=n;
								break;
							}
						}
					}
					if(i==data.size()-2){
						isMovement=false;
					}
				}
				
				movement.setEndTime(currTime);
				movement.getEndTime().addTime(epochPeriod);
				movement.setIntensity(avgIntensity/(i-i_before));
				result.addMovement(movement);
			}
			currTime.addTime(epochPeriod);
		}
		result.setEndTime(currTime);

		return result;
	}

	public TimeSeries getTimeSeries(){
		TimeSeries series = new TimeSeries("IntensityOfMovement");		
		ExtCalendar currTime=(ExtCalendar) startTime.clone();
		currTime.add(Calendar.MILLISECOND, shift);
		double lastIntensity=0d;
		double nextIntensity=getMovementIntensity(data.get(0));

		for(int i=1;i<data.size()-1 && currTime.before(endShowTime);i++){
			currTime.addTime(epochPeriod);

			Integer[] currData=data.get(i);

			double intensity=nextIntensity;
			nextIntensity=getMovementIntensity(currData);

			if(intensity!=lastIntensity || intensity!=nextIntensity){
				series.addOrUpdate(currTime.getRegularTimePeriod(),intensity);
			}
			lastIntensity=intensity;
		}

		return series;
	}

	public static int preview(String path) throws Exception{
		int result=0;
		boolean mobileFile=false;
		boolean rawFile=false;

		File file = new File(path);
		BufferedReader fReader=null;
		fReader = new BufferedReader(new FileReader(file));

		String line = null;

		line = fReader.readLine();

		//String serialNumber = fReader.readLine().replace("Serial Number: ", "");
		fReader.readLine();

		SimpleDateFormat sdfToDate = new SimpleDateFormat( "dd.MM.yyyy HH:mm:ss" );
		String sStartTime = fReader.readLine().replace("Start Time ", "");
		String sStartDate = fReader.readLine().replace("Start Date ", "");
		ExtCalendar startTime = new ExtCalendar();
		startTime.setTime(sdfToDate.parse(sStartDate+" "+sStartTime));
		//ExtCalendar endTime=(ExtCalendar) startTime.clone();
		
		//SimpleDateFormat sdf2ToDate = new SimpleDateFormat( "HH:mm:ss" );
		line=fReader.readLine();
		if(!line.substring(14,16).equals("hh")){
			mobileFile=true;
		}

		String sDownlaodTime = fReader.readLine().replace("Download Time ", "");
		String sDownloadDate = fReader.readLine().replace("Download Date ", "");
		ExtCalendar downloadTime = new ExtCalendar();
		downloadTime.setTime(sdfToDate.parse(sDownloadDate+" "+sDownlaodTime));

		//String memoryAddress=fReader.readLine().replace("Current Memory Address: ", "");
		fReader.readLine();
		
		line=fReader.readLine();
		//String batteryVoltage=line.replace("Current Battery Voltage: ", "").replaceAll("Mode(.*)", "").trim();
		int mode=Integer.parseInt(line.substring(line.indexOf("Mode = ")+7,line.length()));
		if(mode==12){
			rawFile=true;
		}
		line = fReader.readLine();

		Integer[] oldElements=new Integer[5];
		Integer[] elements=new Integer[5];
		while((line = fReader.readLine()) != null)
		{
			try{
				StringTokenizer st = new StringTokenizer(line,",");
				elements=new Integer[st.countTokens()];

				for(int i=0;i<elements.length;i++){
					String element=st.nextToken();
					if(mobileFile){
						elements[i]=new Float((new Float(element))*1000f).intValue();						
					}else{
						elements[i]=new Integer(element);
					}
				}
				if(getPreviewIntensity(elements,mobileFile,rawFile)==0 && getPreviewIntensity(oldElements,mobileFile,rawFile)>0){
					result++;
				}
			}catch(Exception e){
				for(int i=0;i<5;i++){
					elements[i]=0;
				}
				if(getPreviewIntensity(elements,mobileFile,rawFile)==0 && getPreviewIntensity(oldElements,mobileFile,rawFile)>0){
					result++;
				}
			}
			oldElements=elements;
		}
		fReader.close();

		return result;
	}

	public String getDescription(){
		String result="GT3X: "+filePath+"\n\n";
		result+="Serial Number: "+serialNumber+"\n";
		result+="Memory Address: "+memoryAddress+"\n";
		result+="Battery Voltage: "+batteryVoltage+"\n";
		result+="Start Time: "+startTime.getTime().toString()+"\n";
		result+="End Time: "+endTime.getTime().toString()+"\n";
		result+="Epoch Period: "+epochPeriod.getHMS()+"\n";
		result+="Download Time: "+downloadTime.getTime().toString();
		return result;
	}

	public void setSerialNumber(String serialNumber) {
		this.serialNumber = serialNumber;
	}

	public String getSerialNumber() {
		return serialNumber;
	}

	public void setMemoryAddress(String memoryAddress) {
		this.memoryAddress = memoryAddress;
	}

	public String getMemoryAddress() {
		return memoryAddress;
	}

	public ExtCalendar getEndShowTime() {
		return endShowTime;
	}

	public void setEndShowTime(ExtCalendar endShowTime) {
		this.endShowTime = (ExtCalendar) endShowTime.clone();
	}

	public void setBatteryVoltage(String batteryVoltage) {
		this.batteryVoltage = batteryVoltage;
	}

	public String getBatteryVoltage() {
		return batteryVoltage;
	}

	public void setStartTime(ExtCalendar startTime) {
		this.startTime = startTime;
	}

	public ExtCalendar getStartTime() {
		return startTime;
	}

	public void setDownloadTime(ExtCalendar downloadTime) {
		this.downloadTime = downloadTime;
	}

	public ExtCalendar getDownloadTime() {
		return downloadTime;
	}

	public void setEpochPeriod(ExtCalendar epochPeriod) {
		this.epochPeriod = epochPeriod;
	}

	public ExtCalendar getEpochPeriod() {
		return epochPeriod;
	}
	public ArrayList<Integer[]> getData() {
		return data;
	}
	
	public MovementSeries getMData() {
		// this is just to satisfy the Interface requirements.
		return null;
	}

	public void setData(ArrayList<Integer[]> data) {
		this.data = data;
	}

	public boolean validate() {
		return isValidFile;
	}

	public void setShift(int shift) {
		this.shift = shift;
	}

	public int getShift() {
		return shift;
	}

	public void setEarthGravity(int earthGravity) {
		this.earthGravity = earthGravity;
	}

	public int getEarthGravity() {
		return earthGravity;
	}

	public boolean isMobileFile() {
		return mobileFile;
	}

	public void setMobileFile(boolean mobileFile) {
		this.mobileFile = mobileFile;
	}

	public boolean isRawFile() {
		return rawFile;
	}

	public void setRawFile(boolean rawFile) {
		this.rawFile = rawFile;
	}

	public boolean loadFile(String path, JFrame frame){
		Logger.addEntry("Loading GT3X-File: "+path);
		File file = new File(path);
		BufferedReader fReader=null;
		try {
			fReader = new BufferedReader(new FileReader(file));
		} catch (FileNotFoundException e) {
			Logger.addEntry("Error loading file: "+path+", File not found");
			JOptionPane.showMessageDialog(frame, "Error loading file: "+path+"\nFile not found");
			return false;
		}
	
		filePath=path;
		String line = null;
	
		try {
			line = fReader.readLine();
			serialNumber = fReader.readLine().replace("Serial Number: ", "");
			Logger.addEntry("Serial Number: "+serialNumber);
	
			SimpleDateFormat sdfToDate = new SimpleDateFormat( "dd.MM.yyyy HH:mm:ss" );
			String sStartTime = fReader.readLine().replace("Start Time ", "");
			String sStartDate = fReader.readLine().replace("Start Date ", "");
			startTime.setTime(sdfToDate.parse(sStartDate+" "+sStartTime));
			Logger.addEntry("Start Time: "+startTime.getTime().toString());
			endTime=(ExtCalendar) startTime.clone();
	
			SimpleDateFormat sdf2ToDate = new SimpleDateFormat( "HH:mm:ss" );
			line=fReader.readLine();
			if(line.substring(14,16).equals("hh")){
				String sEpoch = line.replace("Epoch Period (hh:mm:ss) ", "");
				epochPeriod.setTime(sdf2ToDate.parse(sEpoch));
			}else{
				mobileFile=true;
				String sMS = line.replace("Epoch Period (ms) ", "");
				int iMS=Integer.parseInt(sMS);
				epochPeriod.setTime(sdf2ToDate.parse("00:00:00"));
				epochPeriod.add(Calendar.MILLISECOND, iMS);
			}
	
			String sDownlaodTime = fReader.readLine().replace("Download Time ", "");
			String sDownloadDate = fReader.readLine().replace("Download Date ", "");
			downloadTime.setTime(sdfToDate.parse(sDownloadDate+" "+sDownlaodTime));
			Logger.addEntry("Download Time: "+downloadTime.getTime().toString());
			Logger.addEntry("Epoch Period: "+epochPeriod.getHMS());
	
			memoryAddress=fReader.readLine().replace("Current Memory Address: ", "");
			line=fReader.readLine();
			batteryVoltage=line.replace("Current Battery Voltage: ", "").replaceAll("Mode(.*)", "").trim();
			mode=Integer.parseInt(line.substring(line.indexOf("Mode = ")+7,line.length()));
			if(mode==12){
				epochPeriod.setTime(sdf2ToDate.parse("00:00:00"));
				epochPeriod.add(Calendar.MILLISECOND, 33);
				rawFile=true;
			}
				
			isValidFile = true;
	
			line = fReader.readLine();
		} catch (Exception e) {
			Logger.addEntry("Error loading file: "+path+", Malformed header");
			JOptionPane.showMessageDialog(frame, "Error loading file: "+path+"\nMalformed header");
			isValidFile = false;
			return false;
		}
	
		try {
			int lineCounter=0;
			while((line = fReader.readLine()) != null)
			{
				lineCounter++;
				try{
					StringTokenizer st = new StringTokenizer(line,",");
					
					Integer[] elements=new Integer[st.countTokens()];
	
					for(int i=0;i<elements.length;i++){
						String element=st.nextToken();
	
						if(mobileFile){
							elements[i]=new Float((new Float(element))*1000f).intValue();
						}else{
							elements[i]=new Integer(element);
						}
					}
					data.add(elements);
				}catch(Exception e){
					data.add(new Integer[5]);
					Logger.addEntry("	Error: malformed line @ line"+lineCounter);
				}
				endTime.addTime(epochPeriod);
			}
			fReader.close();
		}catch (IOException e) {
			Logger.addEntry("Error loading file: "+path+"\n"+e.getMessage());
			JOptionPane.showMessageDialog(frame, "Error loading file: "+path+"\n"+e.getMessage());
			return false;
		}
		Logger.addEntry("End Time: "+endTime.getTime().toString());
		Logger.addEntry("GT3X-File loaded, "+data.size()+" entries found");
		return true;
	}

	public void setTreshold(int treshold) {
		this.treshold = treshold;
	}

	public int getTreshold() {
		return treshold;
	}

}
